package com.qcrud.core;

import com.qcrud.core.result.QueryResult;
import com.qcrud.core.transaction.LocalTransactionHandler;
import com.qcrud.core.transaction.TransactionException;
import com.qcrud.core.transaction.TransactionHandler;
import com.qcrud.core.transaction.TransactionIsolationLevel;
import lombok.extern.slf4j.Slf4j;

import java.io.Closeable;
import java.sql.Connection;
import java.sql.SQLException;

@Slf4j
public class Handle implements AutoCloseable {
    private final StatementContext ctx;
    private final Connection connection;
    private final TransactionHandler transactions;

    public Handle(ConnectionFactory connectionFactory) {
        this.ctx = new StatementContext(connectionFactory);
        this.connection = this.ctx.getConnection();
        this.transactions = new LocalTransactionHandler();
    }

    public boolean execute(String sql, Object... args) throws SQLException {
        SqlStatement stmt = this.ctx.createSqlStatement(sql);
        return stmt.bindArgs(args).execute();
    }

    public QueryResult createQuery(String sql, Object... args) throws SQLException {
        SqlStatement stmt = this.ctx.createSqlStatement(sql);
        return new QueryResult(stmt.bindArgs(args).executeQuery());
    }

    public int createUpdate(String sql, Object... args) throws SQLException {
        SqlStatement stmt = this.ctx.createSqlStatement(sql);
        return stmt.bindArgs(args).executeUpdate();
    }

    public StatementContext getStatementContext() {
        return this.ctx;
    }

    public Connection getConnection() {
        return this.connection;
    }

    public Handle begin() {
        transactions.begin(this);
        return this;
    }

    public Handle commit() {
        transactions.commit(this);
        return this;
    }

    public Handle rollback() {
        transactions.rollback(this);
        return this;
    }

    public boolean isInTransaction() {
        return transactions.isInTransaction(this);
    }

    public TransactionIsolationLevel getTransactionIsolationLevel() {
        try {
            return TransactionIsolationLevel.valueOf(connection.getTransactionIsolation());
        } catch (SQLException e) {
            throw new TransactionException("unable to access current setting", e);
        }
    }

    public void setTransactionIsolation(TransactionIsolationLevel transactionIsolationLevel) {
        int level = transactionIsolationLevel.intValue();
        try {
            if (connection.getTransactionIsolation() != level) {
                connection.setTransactionIsolation(level);
            }
        } catch (SQLException e) {
            throw new TransactionException("set transaction isolation level " + level, e);
        }
    }

    public <R, X extends Exception> R inTransaction(HandleCallback<R, X> callback) throws X {
        return isInTransaction()
                ? callback.withHandle(this)
                : transactions.inTransaction(this, callback);
    }

    public <X extends Exception> void useTransaction(final HandleConsumer<X> consumer) throws X {
        inTransaction(consumer.asCallback());
    }

    public <R, X extends Exception> R inTransaction(TransactionIsolationLevel level, HandleCallback<R, X> callback) throws X {
        if (isInTransaction()) {
            TransactionIsolationLevel currentLevel = getTransactionIsolationLevel();
            if (currentLevel != level && level != TransactionIsolationLevel.UNKNOWN) {
                throw new TransactionException(
                        "Tried to execute nested transaction with isolation level " + level + ", "
                                + "but already running in a transaction with isolation level " + currentLevel + ".");
            }
            return callback.withHandle(this);
        }

        try (TransactionResetter tr = new TransactionResetter(getTransactionIsolationLevel())) {
            setTransactionIsolation(level);
            return transactions.inTransaction(this, level, callback);
        }
    }

    public <X extends Exception> void useTransaction(TransactionIsolationLevel level, HandleConsumer<X> consumer) throws X {
        inTransaction(level, consumer.asCallback());
    }

    private class TransactionResetter implements Closeable {

        private final TransactionIsolationLevel initial;

        TransactionResetter(TransactionIsolationLevel initial) {
            this.initial = initial;
        }

        @Override
        public void close() {
            setTransactionIsolation(initial);
        }
    }

    @Override
    public void close() throws Exception {
        if (!this.isInTransaction()) {
            /**
             * 非开启事务关闭连接
             */
            this.connection.close();
        }
    }
}
